package com.zws.cucumber.expander;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;

import java.util.Arrays;
import java.util.Stack;

/**
 * author: zws
 */
public class ZwsStrBasicExpander {

    private static ObjectMapper objectMapper = new ObjectMapper();
    private static ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter();
    protected final String input;
    protected final int length;
    protected int index;
    protected int level;
    protected Stack<Integer> startIndexStack = new Stack<>();
    protected ZwsStrOption option = ZwsStrOption.DefaultOption;

    protected boolean debug = false;
    protected ZwsStrBasicExpander parent;
    protected int parentLevel = 0;

    protected ZwsStrBasicExpander(String input, ZwsStrBasicExpander parent, boolean debug, int level) {
        this.input = input;
        this.length = input.length();
        this.index = 0;
        this.parent = parent;
        if (parent != null) {
            this.debug = parent.debug;
            this.parentLevel = parent.level();
        }
        this.debug |= debug;
        this.level = level;
    }

    protected void checkLength(int index) {
        if (index >= length) {
            throw new IllegalArgumentException();
        }
    }

    protected char charAt(int index) {
        checkLength(index);
        return input.charAt(index);
    }
    protected boolean expectCurrent(char expected) {
        return expect(index, expected);
    }

    protected boolean expectCurrent(char expected1, char expected2) {
        return expect(index, expected1) || expect(index, expected2);
    }

    protected boolean expectCurrent(char ... expecteds) {
        for (char expected : expecteds) {
            if (expect(index, expected)) {
                return true;
            }
        }
        return false;
    }


    protected boolean expectNext(char expected) {
        return expect(index + 1, expected);
    }
    private boolean expect(int index, char expected) {
        if (index < length) {
            return charAt(index) == expected;
        }
        return false;
    }

    protected boolean match(char expected) {
        checkLength(index);
        char actual;
        if ((actual = charAt(index++)) != expected) {
            throw new IllegalStateException(
                "expected: '" + expected + "', "
                    + "actual: '" + actual + "', "
                    + "index: " + (index - 1) + "\n"
                    + "Parse info stack: \n" + generateParseStack());
        }
        return true;
    }

    public void skipWhiteSpaces() {
        while (expectCurrent(ZwsStrConst.SYMBOL_WS)) {
            match(ZwsStrConst.SYMBOL_WS);
        }
    }

    protected void increaseLevel() {
        level++;
    }

    protected void decreaseLevel() {
        level--;
    }

    protected int level() {
        return parent != null ? parentLevel + level : level;
    }

    protected void markStartIndex() {
        startIndexStack.push(index);
    }

    protected void unmarkStartIndex() {
        startIndexStack.pop();
    }

    protected int startIndex() {
        if (!startIndexStack.empty()) {
            return startIndexStack.peek();
        }
        return 0;
    }

    public String parseInfo() {
        return "Index: " + index + " of input: " + input;
    }

    protected String getExceptionPositionMessage() {
        char[] paddings = new char[index];
        Arrays.fill(paddings, ' ');
        Arrays.fill(paddings, startIndex(), index - 1, '-');
        paddings[index-1] = '^';
        return "\nException occur at\n"
                + "  " + input + "\n"
                + "  " + new String(paddings) + "\n";
    }

    public String generateParseStack() {
        StringBuilder sb = new StringBuilder(256);
        sb.append(parseInfo()).append("\n");
        ZwsStrBasicExpander parent0 = parent;
        while (parent0 != null) {
            sb.append("    ").append(parent0.parseInfo()).append("\n");
            parent0 = parent0.parent;
        }
        return sb.toString();
    }

    protected void parseOption() {
        option = ZwsStrOption.DefaultOption;
        if (expectCurrent('>')) {
            if (expectNext('>')) {
                index++;
            } else {
                int startIndex = index;
                while (!expectCurrent('<')) {
                    index++;
                }
                match('<');
                match(' ');
                String optionString = input.substring(startIndex, index);
                option = ZwsStrOption.parse(optionString);
                if (option.isDebug()) {
                    this.debug = true;
                }
            }
        }
    }

    protected String prettyPrintIfNeeded(String output) {
        if (option.isPrettyPrintJson()) {
            try {
                return objectWriter.writeValueAsString(objectMapper.readValue(output, Object.class));
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        }
        return output;
    }

}
